//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.filesharing;

import java.io.*;
import java.util.*;
import java.beans.*;

import ants.p2p.*;
import ants.p2p.messages.*;
import ants.p2p.utils.*;

import org.apache.log4j.*;

public class PartialFile{

  static Logger _logger = Logger.getLogger(PartialFile.class.getName());

  File f = null;
  File downloadInfos = null;
  String name;
  String source;
  long fileSize;
  byte[] hash;
  ByteWrapper[] fileParts;
  FilePushMessage fpm;
  public long lastId = 0;
  int groupFactor;
  long lastActivityTime = System.currentTimeMillis();

  PropertyChangeSupport pcs;

  public static int computeGroupFactor(int blockSize) throws Exception{
    if(blockSize >= (int)Math.pow(2,20))
      return 1;
    else if(blockSize <= (int)Math.pow(2,16))
      return 10;
    else{
      double logarithm = (Math.log(blockSize)/Math.log(2));
      if(logarithm - Math.floor(logarithm) == 0){
        blockSize = blockSize / (int)Math.pow(2,16);
        blockSize = 10 - (int)Math.floor((Math.log(blockSize)/Math.log(2)) * 2);
        return blockSize;
      }else{
        throw new Exception("BlockSize is not a power of 2: "+blockSize);
      }
    }
  }

  public long getLastId(){
    return this.lastId;
  }

  public String getName(){
    return this.name;
  }

  public byte[] getHash(){
    return this.hash;
  }

  public long getLastActivityTime(){
    return this.lastActivityTime;
  }

  public PartialFile(String name, String source, byte[] hash, boolean resume, long offset, FilePushMessage fpm, PropertyChangeSupport pcs) throws Exception{
    name = WarriorAnt.chunksHome + name;
    downloadInfos = new File(name + ".tmp");
    if(downloadInfos.exists() && resume == false){
      ObjectInputStream fis = new ObjectInputStream(new FileInputStream(
          downloadInfos));
      String oldName = (String)fis.readObject();
      fis.close();
      f = new File(oldName);
      if(f.exists())
        f.delete();
      f = new File(name);
      if(f.exists())
        f.delete();
      ObjectOutputStream fos = new ObjectOutputStream(new FileOutputStream(
          downloadInfos,false));
      fos.writeObject(name);
      fos.close();
    }
    else if(!downloadInfos.exists() && resume == true){
      throw new Exception("Resuming error");
    }
    else if(downloadInfos.exists() && resume == true){
      ObjectInputStream fis = new ObjectInputStream(new FileInputStream(
          downloadInfos));
      String oldName = (String)fis.readObject();
      fis.close();
      f = new File(oldName);
      if(f.length() != offset)
        throw new Exception("Resuming error");
    }else{
      f = new File(name);
      if(f.exists())
        f.delete();
      ObjectOutputStream fos = new ObjectOutputStream(new FileOutputStream(
          downloadInfos,false));
      fos.writeObject(name);
      fos.close();
    }
    this.pcs = pcs;
    this.fpm = fpm;
    this.name = name;
    this.source = source;
    this.hash = hash;
    this.fileSize = fpm.getFileSize().longValue();
    this.groupFactor = computeGroupFactor(fpm.getBlockSize().intValue());
    fileParts = new ByteWrapper[groupFactor];
  }

  public FilePushMessage getFilePushMessage(){
    return this.fpm;
  }

  public synchronized void appendBytes(byte[] b, long id){
    try{
      if(id < (lastId * groupFactor)){
        return;
      }
      if(fileParts[(int)(id - (lastId * groupFactor))]!=null){
        return;
      }
      _logger.debug("Writing to memory part " + id);
      this.pcs.firePropertyChange("bytesGroupAppendedToFilePart", null, this);
      fileParts[ (int) (id - (lastId * groupFactor))] = new ByteWrapper(b);
      if (receivedBlock()) {
        finalizeFile();
        this.pcs.firePropertyChange("byteBlockFinalizedInFilePart", null, this);
        lastId++;
      }
      this.lastActivityTime = System.currentTimeMillis();
    }catch(Exception e){_logger.error("",e);}
  }

  public boolean receivedBlock(){
    for(int x = 0 ; x < fileParts.length; x++){
      if(fileParts[x]==null)
        return false;
    }
    return true;
  }

  public synchronized void finalizeFile(){
    try {
      _logger.debug("Finalizing");
      FileOutputStream fos = new FileOutputStream(f, true);
      for (int x = 0; x < fileParts.length; x++) {
        if (fileParts[x] != null) {
          fos.write( ( (ByteWrapper) fileParts[x]).content);
        }
        else
          break;
      }
      for (int x = 0; x < fileParts.length; x++) {
        fileParts[x] = null;
      }
      fos.close();
      this.downloadInfos.delete();
      System.gc();
    }
    catch (Exception e) {
      _logger.error("",e);
    }
  }

  public boolean equals(Object o){
    if(o instanceof PartialFile)
      return compareHash(((PartialFile)o).hash,this.hash)&&
         ((PartialFile)o).getFilePushMessage().equals(this.getFilePushMessage());
    else
      return o==this;
  }

  public long getReceivedSize(){
    return f.length();
  }

  public static boolean compareHash(byte[] h1, byte[] h2) {
    if (h1.length != h2.length)
      return false;
    for (int x = 0; x < h1.length; x++) {
      if (h1[x] != h2[x])
        return false;
    }
    return true;
  }
}

class ByteWrapper{
  byte[] content;
  public ByteWrapper(byte[] content){
    this.content=content;
  }
}
